package controller

import (
	"context"
	"encoding/json"

	"github.com/go-logr/logr"

	"github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
	"github.com/aquasecurity/trivy-operator/pkg/exposedsecretreport"
	"github.com/aquasecurity/trivy-operator/pkg/kube"
	"github.com/aquasecurity/trivy-operator/pkg/sbomreport"
	"github.com/aquasecurity/trivy-operator/pkg/trivyoperator"
	"github.com/aquasecurity/trivy-operator/pkg/vulnerabilityreport"
)

const (
	secretSizeLimit = 1048576
)

func hasVulnerabilityReports(ctx context.Context, vulnReadWriter vulnerabilityreport.ReadWriter, owner kube.ObjectRef, hash string, images kube.ContainerImages) (bool, error) {
	// TODO FindByOwner should accept optional label selector to further narrow down search results
	list, err := vulnReadWriter.FindByOwner(ctx, owner)
	if err != nil {
		return false, err
	}

	actual := make(map[string]bool)
	for _, report := range list {
		if containerName, ok := report.Labels[trivyoperator.LabelContainerName]; ok {
			if hash == report.Labels[trivyoperator.LabelResourceSpecHash] {
				actual[containerName] = true
			}
		}
	}

	return compareReports(actual, images), nil
}

func hasSecretReports(ctx context.Context, esReadWriter exposedsecretreport.ReadWriter, owner kube.ObjectRef, hash string, images kube.ContainerImages) (bool, error) {
	// TODO FindByOwner should accept optional label selector to further narrow down search results
	list, err := esReadWriter.FindByOwner(ctx, owner)
	if err != nil {
		return false, err
	}

	actual := make(map[string]bool)
	for _, report := range list {
		if containerName, ok := report.Labels[trivyoperator.LabelContainerName]; ok {
			if hash == report.Labels[trivyoperator.LabelResourceSpecHash] {
				actual[containerName] = true
			}
		}
	}

	return compareReports(actual, images), nil
}

func compareReports(actual map[string]bool, images kube.ContainerImages) bool {
	// If lengths are different, reports do not match
	if len(actual) != len(images) {
		return false
	}

	// Check if all container names in images exist in actual
	for containerName := range images {
		if !actual[containerName] {
			return false
		}
	}

	return true
}

func getGlobalSbomReports(ctx context.Context, sbomReadWriter sbomreport.ReadWriter, images kube.ContainerImages, log logr.Logger) map[string]v1alpha1.SbomReportData {
	sbomReportDataMap := make(map[string]v1alpha1.SbomReportData, 0)
	for name, ref := range images {
		list, err := sbomReadWriter.FindReportByImageRef(ctx, ref)
		if err != nil {
			log.V(1).Info("cached sbom not found for image ref - running standard scan-job", "image-ref", ref)
			return make(map[string]v1alpha1.SbomReportData)
		}
		if len(list) > 0 {
			for _, data := range list {
				if isSbomExceededSecretSizeLimit(data.Report.Bom, secretSizeLimit) {
					log.V(1).Info("cached sbom size exceeded secret size limit for image ref - running standard scan-job", "image-ref", ref)
					return make(map[string]v1alpha1.SbomReportData)
				}
				sbomReportDataMap[name] = data.Report
			}
		}
	}
	return sbomReportDataMap
}

func isSbomExceededSecretSizeLimit(bom v1alpha1.BOM, maxSecretSize int) bool {
	bomByte, err := json.Marshal(bom)
	if err != nil {
		return true
	}
	if len(bomByte) >= maxSecretSize {
		return true
	}
	return false
}
